#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#define BUF_LEN 100

#include "mychar.h"

//主设备号
int major = 11;
//次设备号
int minor = 0;
//申请数量
int mychar_num = 1;

//自定义字符设备
struct mychar_cdev {

	//定义字符设备
	struct cdev mydev;
	//模拟设备的数据
	char mychar_buf[BUF_LEN];
	//模拟当前有多少数据
	int curlen;

};

struct mychar_cdev gdev;

int mychar_open(struct inode *pnode, struct file *pfile){

	//将自定义字符设备的地址赋值给 private_data 变量
	//这样后续的操作函数都不需要使用全局变量
	pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_cdev, mydev);
	printk("mychar_open is called \n");
	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile){
	printk("mychar_close is called \n");
	return 0;
}


//读取设备产生的数据
ssize_t mychar_read(struct file *pfile, char __user *pbuf, size_t count, loff_t *ppos){

	//拿到自定义字符设备的地址
	struct mychar_cdev *pmychardev = (struct mychar_cdev *)pfile->private_data;

	int size = 0;
	int ret = 0;

	//实际返回应用层的数据长度
	if(count > pmychardev->curlen){
		size = pmychardev->curlen;
	}else{
		size = count;
	}

	//将内核数据复制到用户空间中
	ret = copy_to_user(pbuf, pmychardev->mychar_buf, size);
	if(ret){
		printk("copy_to_user failed\n");
		return -1;
	}

	//更新数据以及curlen
	pmychardev->curlen -= size;
	memcpy(pmychardev->mychar_buf, pmychardev->mychar_buf + size, pmychardev->curlen);
	return size;
}

//向设备写入数据
ssize_t mychar_write(struct file *pfile, const char __user *pbuf, size_t count, loff_t *ppos){

	//拿到自定义字符设备的地址
	struct mychar_cdev *pmychardev = (struct mychar_cdev *)pfile->private_data;

	int size = 0;
	int ret = 0;

	//实际写入的数据长度
	if(count > BUF_LEN - pmychardev->curlen){
		size = BUF_LEN - pmychardev->curlen;
	}else{
		size = count;	
	}

	//将用户空间的数据写入到内核空间中
	ret = copy_from_user(pmychardev->mychar_buf + pmychardev->curlen, pbuf, size);
	if(ret){
		printk("copy_from_user failed\n");
		return -1;
	}

	//更新数据以及curlen
	pmychardev->curlen += size;

	return size;
}

long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg){

	//拿到自定义字符设备的地址
	struct mychar_cdev *pmychardev = (struct mychar_cdev *)pfile->private_data;

	//将arg先转换成用户空间指针形式
	int __user *uarg = (int * )arg;
	int buffLen = BUF_LEN;
	int ret = 0;

	switch(cmd){
	case MY_CHAR_MAX_LEN :
		ret = copy_to_user(uarg, &buffLen, 1);
		if(ret){
			printk("ioctl copy_to_user failed\n");
			return -1;
		}
		break;
	case MY_CHAR_CUR_LEN:
		ret = copy_to_user(uarg, &pmychardev->curlen, 1);
		if(ret){
			printk("ioctl copy_to_user failed\n");
			return -1;	
		}
		break;
	default:
		return -1;
	}
	return 0;
}


//定义操作函数
struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	//设备号
	dev_t devno = MKDEV(major, minor);
	//从当前设备号开始验证，mychar_num数量的设备号。若系统没有占用，则申请占用mychar_num数量的设备号
	//"mychar": 该设备号在/proc/devices文件中的名称。意义：确保设备号申请成功，及方便后续据此设备号、名称创建设备文件
	ret = register_chrdev_region(devno, mychar_num, "mychar");

	if(ret){
		//系统动态分配
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		//此时设备号是由系统分配的
		major = MAJOR(devno);
	}

	//初始化字符设备,指定所属内核模块，指定操作函数集
	gdev.mydev.owner = THIS_MODULE;
	cdev_init(&gdev.mydev, &myops);

	//将cdev对象添加到内核的字符设备链表中
	cdev_add(&gdev.mydev, devno, mychar_num);


	return 0;
}

void __exit mychar_exit(void)
{

	dev_t devno = MKDEV(major, minor);

	//从内核的字符设备链表中移除该设备
	cdev_del(&gdev.mydev);

	//释放设备号
	unregister_chrdev_region(devno, mychar_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
